
#include "stdafx.h"
#include "desc_compiler.h"

#include "ds\ds_tokenizer.h"

namespace dc {

#define MATCH(TOKEN) \
   if (parseInfo.cur->token != TOKEN) { \
      error += dsSPrintf("%s(%d): '%s': unexpected token\n", parseInfo.source.CStr(), parseInfo.sourceLine + parseInfo.cur->line, parseInfo.cur->value.CStr()); \
      return false; \
   } \
   ++parseInfo.cur;

#define MATCH_VAL(TOKEN, VAL) \
   if (parseInfo.cur->token != TOKEN || parseInfo.cur->value != VAL) { \
      error += dsSPrintf("%s(%d): '%s': unexpected token, symbol '%s' expected\n", parseInfo.source.CStr(), parseInfo.sourceLine + parseInfo.cur->line, parseInfo.cur->value.CStr(), cstr(VAL)); \
      return false; \
   } \
   ++parseInfo.cur;

#define MATCH_WORD(VAL)    MATCH_VAL(TOKEN_WORD, VAL)
#define MATCH_SYMBOL(VAL)  MATCH_VAL(TOKEN_SYMBOL, VAL)

#define MATCH_ANY_VALUE() \
   switch (parseInfo.cur->token) { \
      case TOKEN_INT: case TOKEN_FLOAT: \
      case TOKEN_STRING: case TOKEN_WORD: \
         MATCH_ANY(); \
         break; \
      default: \
         MATCH_NOTHING(); \
         break; \
   }
#define MATCH_ANY()        ++parseInfo.cur;
#define MATCH_NOTHING()    MATCH(TOKEN_EOF)

#define NEXT_IS(TOKEN, VAL) (parseInfo.cur->token == TOKEN && parseInfo.cur->value == VAL)
#define NEXT_2_IS(TOKEN, VAL) (parseInfo.cur[1].token == TOKEN && parseInfo.cur[1].value == VAL)
#define NEXT_IS_SYMBOL(VAL) NEXT_IS(TOKEN_SYMBOL, VAL)
#define NEXT_2_IS_SYMBOL(VAL) NEXT_2_IS(TOKEN_SYMBOL, VAL)

#define CUR_TOKEN()        (*parseInfo.cur)

#define CALL(X) \
   if (!(X(parseInfo))) { \
      return false; \
   }

/******************************************************************************/
/* dc::COMPILER::ParseString
/*
/******************************************************************************/
bool COMPILER::Parse(const dsSTRING & text, CONTEXT & ctxt)
{
   error.Clear(); // clear error message

   ds::CPP_LIKE_TOKENIZER tokenizer;
   tokenizer.RegisterCppSymbols(TOKEN_SYMBOL);

   dsVECTOR<TOKEN> tokens;

   COMPILER_ERROR_CALLBACK callback(this, ctxt.file, ctxt.line);
   if (!tokenizer.Tokenize(text, tokens, &callback)) {
      return false;
   }

   if (tokens.IsEmpty()) {
      return true;
   }

   TOKEN token = tokens.Back();
   token.token = TOKEN_EOF;
   tokens += token;

   PARSE_INFO parseInfo;
   parseInfo.source = ctxt.file;
   parseInfo.sourceLine = ctxt.line;
   parseInfo.curContext = &ctxt;
   parseInfo.cur = tokens.Begin();

   CALL( Parse );

   return true;
}

/******************************************************************************/
/* dc::COMPILER::Parse
/*
/******************************************************************************/
bool COMPILER::Parse(PARSE_INFO & parseInfo)
{
   while (parseInfo.cur->token != TOKEN_EOF) {
      if (NEXT_IS_SYMBOL(";")) {
         MATCH_SYMBOL(";"); // skip useless dcomma symbols
         continue;
      }

      if (NEXT_IS(TOKEN_WORD, "enum")) {
         CALL( ParseEnum );
      }

      CALL( ParseDescStruct );
   }

   return true;
}

/******************************************************************************/
/* dc::COMPILER::ParseAttribs
/*
/******************************************************************************/
bool COMPILER::ParseAttribs(dsVECTOR<ATTRIB> & attribs, const dsVECTOR<dsSTRID> & knownAttribs, PARSE_INFO & parseInfo)
{
   while (NEXT_IS_SYMBOL("[")) {
      MATCH_SYMBOL("[");

      ATTRIB attr;
      attr.name = CUR_TOKEN();
      MATCH(TOKEN_WORD);

      //// check for know attribs
      //bool found = false;
      //for (int i = 0; i < knownAttribs.Length(); ++i) {
      //   if (knownAttribs[i] == attr.name.value) {
      //      found = true;
      //   }
      //}
      //if (!found) {
      //   error += dsSPrintf("%s(%d): '%s': unknown attribute\n", parseInfo.source.CStr(), parseInfo.sourceLine + attr.name.line, attr.name.value.CStr());
      //   return false;
      //}

      if (NEXT_IS_SYMBOL("(")) {
         MATCH_SYMBOL("(");
         for (;;) {
            attr.args += CUR_TOKEN();
            MATCH_ANY_VALUE();

            if (NEXT_IS_SYMBOL(")")) {
               break;
            }
            
            MATCH_SYMBOL(",");
         }
         MATCH_SYMBOL(")");
      }

      MATCH_SYMBOL("]");

      attribs += attr;
   }

   return true;
}

/******************************************************************************/
/* dc::COMPILER::ParseEnum
/*
/******************************************************************************/
bool COMPILER::ParseEnum(PARSE_INFO & parseInfo)
{
   ENUM_INFO e;
   if (!ParseAttribs(e.attribs, descAttribs, parseInfo)) {
      return false;
   }

   // desc declaration
   MATCH_WORD("enum");

   e.source = parseInfo.source;

   e.name = CUR_TOKEN();
   MATCH(TOKEN_WORD);
   MATCH_SYMBOL("{");

   while (!NEXT_IS_SYMBOL("}")) {
      TOKEN value = CUR_TOKEN();
      MATCH(TOKEN_WORD);

      e.values += value;

      if (!NEXT_IS_SYMBOL("}")) { // should be comma, except when there is the end of block (allow to set unused comma, like enum A { bla1, bla2, }
         MATCH_SYMBOL(",");
      }
   }

   MATCH_SYMBOL("}");

   parseInfo.curContext->enums += e;

   return true;
}

/******************************************************************************/
/* dc::COMPILER::ParseDesc
/*
/******************************************************************************/
bool COMPILER::ParseDescStruct(PARSE_INFO & parseInfo)
{
   TYPE_INFO desc;
   if (!ParseAttribs(desc.attribs, descAttribs, parseInfo)) {
      return false;
   }

   // desc declaration
   bool isStruct = false;
   if (NEXT_IS(TOKEN_WORD, "struct")) {
      MATCH_WORD("struct");
      isStruct = true;
   } else {
      MATCH_WORD("desc");
   }

   desc.source = parseInfo.source;

   desc.name = CUR_TOKEN();
   MATCH(TOKEN_WORD);

   if (NEXT_IS_SYMBOL(":")) {
      MATCH_SYMBOL(":");

      TOKEN parent = CUR_TOKEN();
      desc.parentTypeId = parseInfo.curContext->AddType(parent.value);
      MATCH(TOKEN_WORD);
   }

   MATCH_SYMBOL("{");

   parseInfo.curType = &desc;

   while (!NEXT_IS_SYMBOL("}")) {
      if (NEXT_IS_SYMBOL(";")) { // skip empty ';'
         MATCH_SYMBOL(";");
         continue;
      }

      CALL( ParseVar );
   }

   MATCH_SYMBOL("}");

   if (isStruct) {
      parseInfo.curContext->structs += desc;
   } else {
      parseInfo.curContext->descs += desc;
   }

   return true;
}

/******************************************************************************/
/* dc::COMPILER::ParseState
/*
/******************************************************************************/
bool COMPILER::ParseState(const dsVECTOR<ATTRIB> & attribs, PARSE_INFO & parseInfo)
{
   STATE_INFO state;
   state.attribs = attribs;

   MATCH_WORD("state");

   state.name = CUR_TOKEN();
   MATCH(TOKEN_WORD);

   if (NEXT_IS_SYMBOL(";")) { // empty state
      MATCH_SYMBOL(";");

      parseInfo.curType->states += state;
      return true;
   }

   MATCH_SYMBOL("{");

   while (!NEXT_IS_SYMBOL("}")) {
      STATE_VAR_INFO v;
      v.name = CUR_TOKEN();
      MATCH(TOKEN_WORD);

      if (NEXT_IS_SYMBOL("=")) {
         MATCH_SYMBOL("=");

         // only "true" and "false" words available
         if (!NEXT_IS(TOKEN_WORD, "true") && !NEXT_IS(TOKEN_WORD, "false")) {
            MATCH_NOTHING();
         }

         v.value = CUR_TOKEN();
         MATCH(TOKEN_WORD);
      }

      if (!NEXT_IS_SYMBOL("}")) {
         MATCH_SYMBOL(",");
      }

      state.vars += v;
   }

   MATCH_SYMBOL("}");

   parseInfo.curType->states += state;

   return true;
}

/******************************************************************************/
/* dc::COMPILER::ParseVar
/*
/******************************************************************************/
bool COMPILER::ParseVar(PARSE_INFO & parseInfo)
{
   dsVECTOR<ATTRIB> attribs;

   if (!ParseAttribs(attribs, varAttribs, parseInfo)) {
      return false;
   }

   if (NEXT_IS(TOKEN_WORD, "state")) {
      return ParseState(attribs, parseInfo);
   }

   VAR v;
   v.attribs = attribs;

   CALL( ParseType );
   v.type = parseInfo.lastTypeId;

   for (;;) {
      v.name = CUR_TOKEN();
      MATCH(TOKEN_WORD);

      if (NEXT_IS_SYMBOL("=")) {
         MATCH_SYMBOL("=");

         v.value = CUR_TOKEN();
         MATCH_ANY_VALUE();
      }

      parseInfo.curType->vars += v;

      if (!NEXT_IS_SYMBOL(",")) {
         break;
      }

      MATCH_SYMBOL(",");
   }

   MATCH_SYMBOL(";");

   return true;
}

/******************************************************************************/
/* dc::COMPILER::ParseType
/*
/******************************************************************************/
bool COMPILER::ParseType(PARSE_INFO & parseInfo)
{
   if (NEXT_IS(TOKEN_WORD, "set") && NEXT_2_IS_SYMBOL("<")) {
      MATCH_WORD("set");
      MATCH_SYMBOL("<");

      CALL( ParseType );

      MATCH_SYMBOL(">");

      parseInfo.lastTypeId = parseInfo.curContext->AddContainer(VT_SET, parseInfo.lastTypeId);

      return true;
   }

   TOKEN typeName = CUR_TOKEN();
   MATCH(TOKEN_WORD);

   int & typeId = parseInfo.lastTypeId;
   
   typeId = parseInfo.curContext->AddType(typeName.value);

   while (NEXT_IS_SYMBOL("[")) {
      // array declaration
      MATCH_SYMBOL("[");
      MATCH_SYMBOL("]");

      typeId = parseInfo.curContext->AddContainer(VT_ARRAY, typeId);
   }

   return true;
}

/******************************************************************************/
/* dc::COMPILER_ERROR_CALLBACK::Error
/*
/******************************************************************************/
void COMPILER_ERROR_CALLBACK::Error(ds::TOKENIZER_ERROR_TYPE err, int line, char ch)
{
   switch (err) {
      case ds::TOKEN_ERR_UNEXPECTED_SYMBOL:
         compiler->error += dsSPrintf("%s(%d): '%c': unexpected symbol\n", source.CStr(), sourceLine + line, ch);
         break;

      case ds::TOKEN_ERR_EOLN_IN_LITERAL:
         compiler->error += dsSPrintf("%s(%d):  newline in constant\n", source.CStr(), sourceLine + line);
         break;

      case ds::TOKEN_ERR_EOF_IN_COMMENT:
         compiler->error += dsSPrintf("%s(%d): unexpected end of file found in comment\n", source.CStr(), sourceLine + line);
         break;

      default:
         STRONG_ASSERT(false);
         break;
   }
}

/******************************************************************************/
/* dc::CONTEXT::AddType
/*
/******************************************************************************/
int CONTEXT::AddType(dsSTRID name)
{
   for (int i = 0; i < types.Length(); ++i) {
      if (types[i].type != VT_ARRAY && types[i].name == name) {
         return i;
      }
   }

   VAR_TYPE type;
   type.type = VT_USER; // set VT_USER by default
   type.name = name;
   
   types += type;
   return types.Length() - 1;
}

/******************************************************************************/
/* dc::CONTEXT::AddArray
/*
/******************************************************************************/
int CONTEXT::AddContainer(VAR_TYPE_ENUM containerType, int elemTypeId)
{
   STRONG_ASSERT(containerType >= VT_CONTAINER_FIRST);

   for (int i = 0; i < types.Length(); ++i) {
      if (types[i].type == containerType && types[i].typeId == elemTypeId) {
         return i;
      }
   }

   VAR_TYPE type;
   type.type = containerType;
   type.typeId = elemTypeId;

   types += type;
   return types.Length() - 1;
}


/**************************************************************
* CONTEXT::Clear()
*
**************************************************************/
void CONTEXT::Clear (void)
{
   *this = CONTEXT();
}

} // namespace dc
